////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.charts.chartClasses
{

import mx.charts.CategoryAxis;
import mx.charts.effects.effectClasses.SeriesEffectInstance;
import mx.core.mx_internal;
import mx.effects.Effect;
import mx.effects.EffectInstance;
import mx.effects.EffectManager;
import mx.events.CollectionEvent;
import mx.events.FlexEvent;
import mx.graphics.IFill;
import mx.graphics.IStroke;
import mx.charts.ChartItem;

//--------------------------------------
//  Effects
//--------------------------------------

/**
 *  Defines the effect that Flex uses as it hides the current data from view.
 */
[Effect(name="hideDataEffect", event="hideData")]

/**
 *  Defines the effect that Flex uses as it moves the current data
 *  into its final position on the screen.
 */
[Effect(name="showDataEffect", event="showData")]

/**
 *  The Series class is the base class for the classes
 *  that let you specify a data series for a chart control. 
 *  You use the subclasses of the Series class
 *  with the associated chart control. 
 *  You can use a Series class to specify the fill pattern and stroke
 *  characteristics for the chart elements associated with the data series. 
 */
public class Series extends ChartElement
{
    include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  Constructor.
	 */
	public function Series()
	{
		super();
	}

	//--------------------------------------------------------------------------
	//
	//  Variables
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private var _previousTransitionData:Object;

	/**
	 *  @private
	 */
	private var _bDataDirty:Boolean = true;

	/**
	 *  @private
	 */
	private var _bMappingDirty:Boolean = true;

	/**
	 *  @private
	 */
	private var _bFilterDirty:Boolean = true;

	/**
	 *  @private
	 */
	private var _bTransformDirty:Boolean = true;

	/**
	 *  @private
	 */
	private var _bHideTransitionDirty:Boolean = false;

	//--------------------------------------------------------------------------
	//
	//  Overridden properties: dataTransform
	//
	//--------------------------------------------------------------------------
	
    //----------------------------------
	//  dataTransform
    //----------------------------------

	/**
	 *  @inheritDoc
	 */
	override public function set dataTransform(value:DataTransform):void
	{
		if (dataTransform)
		{
			dataTransform.removeEventListener(FlexEvent.TRANSFORM_CHANGE,
									  transformChangeHandler);
		}
		
		super.dataTransform = value;

		if (value)
		{
			value.addEventListener(FlexEvent.TRANSFORM_CHANGE,
								   transformChangeHandler, false, 0, false);
		}
	}

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------
	
    //----------------------------------
	//  displayName
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the displayName property.
	 */
	private var _displayName:String;

	[Inspectable(category="Display")]

	/**
	 *  The name of the series, for display to the user.
	 *  This property is used to represent the series in user-visible labels,
	 *  such as DataTips.
	 */
	public function get displayName():String
	{
		return _displayName;
	}
	
	/**
	 *  @private
	 */
	public function set displayName(value:String):void
	{
		_displayName = value;

		var c:ChartBase = chart;
		if (c)
			c.legendDataChanged();
	}
	
    //----------------------------------
	//  filterData
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the filterData property.
	 */
	private var _filterData:Boolean = true;

	[Inspectable(category="General", defaultValue="true")]

	/**
	 *  This property is <code>true</code> if the series filters its data
	 *  before displaying.
	 *  If a series renders data that contains missing values
	 *  (such as null, undefined, or NaN),
	 *  or renders values outside the range of the chart axes,
	 *  this property should be set to <code>true</code> (the default).
	 *  If you know that all of the data in the series is valid,
	 *  you can set this to <code>false</code> to improve performance
	 */
	public function get filterData():Boolean
	{
		return _filterData;
	}
	
	/**
	 *  @private
	 */
	public function set filterData(value:Boolean):void
	{
		_filterData = value;

		invalidateFilter();
	}
	
    //----------------------------------
	//  interactive
    //----------------------------------

	/**
	 *  @private
	 *  Storage for the interactive property.
	 */
	private var _interactive:Boolean=true;

	[Inspectable(category="General", defaultValue="true")]

	/**
	 *  Determines whether DataTips appear when users interact
	 *  with chart data on the screen.
	 *  Set to <code>false</code> to prevent the series
	 *  from showing DataTips or generating hitData.
	 *
	 *  @default true
	 */
	public function get interactive():Boolean
	{
		return _interactive;
	}

	/**
	 *  @private
	 */
	public function set interactive(value:Boolean):void
	{
		_interactive = value;

		invalidateDisplayList();
	}

    //----------------------------------
	//  legendData
    //----------------------------------

	[Inspectable(environment="none")]

	/**
	 *  An Array of LegendData instances that describe the items
	 *  that should show up in a legend representing this series.
	 *  Derived series classes override this getter and return LegendData
	 *  specific to their styles and data representation method.
	 *  While most series types return only a single LegendData instance,
	 *  some series types, such as PieSeries and  StackedSeries,
	 *  return multiple instances representing individual items
	 *  in the array, or multiple ways of rendering data.	
	 */
	public function get legendData():Array
	{
		return null;
	}
	
    //----------------------------------
	//  renderData
    //----------------------------------

	/**
	 *  Stores the information necessary to render this series.
	 */
	protected function get renderData():Object
	{
		return null;
	}

    //----------------------------------
	//  transitionRenderData
    //----------------------------------

	/** 
	 *  @private
	 *  Storage for the _transitionRenderData property.
	 */
	private var _transitionRenderData:Object;

	/**
	 *  A render data structure passed in by a running transtion.
	 *  When a series effect is set to play on a series, it first captures
	 *  the current state of the series by asking for its render data.
	 *  The transition modifies the render data to create the desired effect,
	 *  and passes the structure back to the series for display.
	 *  If the <code>transitionRenderData</code> property is non-null,
	 *  a series uses its contents to update its display.
	 */
	public function get transitionRenderData():Object
	{
		return _transitionRenderData;
	}

	/** 
	 *  @private
	 */
	public function set transitionRenderData(value:Object):void
	{
		_transitionRenderData = value;
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden methods: UIComponent
	//
	//--------------------------------------------------------------------------

	/**
	 *  Calls the <code>legendDataChanged()</code> method.
	 */
	override public function stylesInitialized():void
	{
		legendDataChanged();
	}

	/**
	 *  @private
	 */
	override public function invalidateSize():void
	{
		invalidateTransform();

		super.invalidateSize();		
	}	

	/**
	 *  @inheritDoc
	 */
	override protected function updateDisplayList(unscaledWidth:Number,
												  unscaledHeight:Number):void
	{
		super.updateDisplayList(unscaledWidth, unscaledHeight);

		if (_bHideTransitionDirty)
			_bHideTransitionDirty = false;

		var s:ChartBase = chart;
		if (s)
			var cs:uint = s.chartState;

		validateData();
		validateTransform();
	}
	
	/**
	 *  @inheritDoc
	 */
	override public function setActualSize(w:Number, h:Number):void
	{
		invalidateTransform();

		super.setActualSize(w, h);
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden methods: ChartElement
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override public function mappingChanged():void
	{
		invalidateMapping();
	}
	
	/**
	 *  @private
	 */
	override public function chartStateChanged(oldState:uint, v:uint):void
	{
		invalidateDisplayList();

		if (v == ChartState.PREPARING_TO_SHOW_DATA || v == ChartState.NONE)
			transitionRenderData = null;
		
		super.chartStateChanged(oldState,v);
	}

	/**
	 *  @private
	 */
	override public function collectTransitions(chartState:Number,
												transitions:Array):void
	{
		var transition:Effect = null;
		var instance:EffectInstance;

		if (chartState == ChartState.PREPARING_TO_HIDE_DATA)
		{
			transition = EffectManager.
				mx_internal::createEffectForType(this,"hideData") as Effect;
			
			if (transition)
			{
				instance = transition.createInstance(this);
				if (instance is SeriesEffectInstance)
					(instance as SeriesEffectInstance).type = "hide";
			}
		}

		else if (chartState == ChartState.PREPARING_TO_SHOW_DATA)
		{
			// Our show effect needs fully calculated and updated caches,
			// so we need to make sure this gets called here.
			validateProperties(); 
			
			transition = EffectManager.
				mx_internal::createEffectForType(this,"showData") as Effect;
			
			if (transition)
			{
				instance = transition.createInstance(this);
				if (instance is SeriesEffectInstance)
					(instance as SeriesEffectInstance).type = "show";
			}
		}

		if (instance != null)
			transitions.push(instance);

		super.collectTransitions(chartState,transitions);
	}

	/**
	 *  @copy mx.charts.chartClasses.IChartElement#claimStyles()
	 */
	override public function claimStyles(styles:Array,
										 firstAvailable:uint):uint
	{
		internalStyleName = styles[firstAvailable];
		return (firstAvailable + 1) % styles.length;
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Caches the values stored in the <code>measureName</code> property
	 *  from the original dataProvider items in the chart item's
	 *  <code>fieldName</code> property.  
	 *  If the <code>measureName</code> property is <code>null</code>
	 *  or the empty string, it assumes the original dataProvider items
	 *  are raw values and caches them instead. 
	 */
	protected function cacheDefaultValues(measureName:String, cache:Array,
										  fieldName:String): Boolean
	{
		var n:int = cache.length;
		var i:int;
		var c:Object;
		
		if (measureName == "" || measureName == null)
		{
			for (i = 0; i < n; i++)
			{
				c = cache[i];
				c[fieldName] = c.item;
			}
			return false;
		}
		else
		{
			for (i = 0; i < n; i++)
			{
				c = cache[i];
				c[fieldName] = c.item[measureName];
			}
			return true;
		}
	}

	/**
	 *  Caches the values stored in the <code>measureName</code> property
	 *  from the original dataProvider items in the chart item's
	 *  <code>fieldName</code> property.  
	*/
	protected function cacheNamedValues(measureName:String, cache:Array,
										fieldName:String):Boolean
	{
		if (measureName == "")
			return false;
		
		var n:int = cache.length;
		for (var i:int = 0; i < n; i++)
		{
			var c:Object = cache[i];
			c[fieldName] = c.item[measureName];
		}
		
		return true;
	}

	/**
	 *  Caches the values stored in the <code>measureName</code> property
	 *  from the original dataProvider items in the chart item's
	 *  <code>fieldName</code> property.  
	 *  If the <code>measureName</code> property is <code>null</code>
	 *  or the empty string, it stores the index of the items
	 *  in the <code>fieldName</code> property instead.
	 */
	protected function cacheIndexValues(measureName:String, cache:Array,
										fieldName:String):Boolean
	{
		var n:int = cache.length;
		var i:int;
		var c:Object;
		
		if (measureName == "")
		{
			for (i = 0; i < n; i++)
			{
				c = cache[i];
				c[fieldName] = c.index;
			}
			return false;
		}
		else
		{
			for (i = 0; i < n; i++)
			{
				c = cache[i];
				c[fieldName] = c.item[measureName];
			}
			return true;
		}

	}
	
	/**
	 *  Extracts the minimum value, maximum value, and, optionally,
	 *  the minimum interval from an array of ChartItems.
	 *  Derived classes can call this method from their 
	 *  DescribeData implementations to fill in the details
	 *  of the DataDescription structure.
	 */
	protected function extractMinMax(cache:Array, measureName:String,
									 desc:DataDescription,
									 calculateInterval:Boolean = false):void
	{
		var n:int = cache.length;
		var i:int;
		var v:Number;
		var prevValidValue:Number;
		var minInterval:Number = Number.POSITIVE_INFINITY;
		
		if (isNaN(desc.min))
		{			
			for (i = 0; i < n; i++)
			{
				v = cache[i][measureName];
				if (isNaN(v) == false)
					break;
			}

			if (isNaN(v))
				return;
			
			desc.min = desc.max = v;
			prevValidValue = v;
		}

		for (i++; i < n; i++)
		{
			v = cache[i][measureName];
			if (isNaN(v))
				continue;
			
			if (v < desc.min)
				desc.min = v;
			if (v > desc.max)
				desc.max = v;	
			
			if (calculateInterval && (v - prevValidValue < minInterval))
				minInterval = Math.abs(v - prevValidValue);
			
			prevValidValue = v;
		}

		if (calculateInterval &&
			minInterval < Number.POSITIVE_INFINITY &&
			(minInterval < desc.minInterval || isNaN(desc.minInterval)))
		{
			desc.minInterval = minInterval;
		}
	}

	/**
	 *  Extracts the minimum value, maximum value, and, optionally,
	 *  the minimum interval from an array of ChartItems.
	 *  Derived classes can call this method from their
	 *  DescribeData implementations to fill in the details
	 *  of the DataDescription structure.
	 */
	protected function extractMinInterval(cache:Array, measureName:String,
										  desc:DataDescription):void
	{
		var n:int = cache.length;
		var i:int;
		var v:Number;
		var prevValidValue:Number;
		var minInterval:Number = Number.POSITIVE_INFINITY;
		
		for (i = 0; i < n; i++)
		{
			v = cache[i][measureName];
			if (isNaN(v) == false)
				break;
		}

		if (isNaN(v))
			return;

		prevValidValue = v;

		for (i++; i < n; i++)
		{
			v = cache[i][measureName];
			if (isNaN(v))
				continue;
			
			if (v - prevValidValue < minInterval)
				minInterval = Math.abs(v - prevValidValue);
			
			prevValidValue = v;
		}

		if (minInterval < Number.POSITIVE_INFINITY &&
			(minInterval < desc.minInterval || isNaN(desc.minInterval)))
		{
			desc.minInterval = minInterval;
		}
	}

	/**
	 *  Removes any item from the provided cache whose <code>field</code>
	 *  property is <code>NaN</code>.
	 *  Derived classes can call this method from their updateFilter()
	 * implementation to remove any ChartItems filtered out by the axes.
	 */
	protected function stripNaNs(cache:Array, field:String):void
	{
		if (filterData == false)
			return;
			
		var len:int = cache.length;
		var start:int = -1;
		var end:int = -1;
		var i:int;

		if (field == "")
		{
			for (i= cache.length - 1; i >= 0; i--)
			{
				if (isNaN(cache[i]))
				{
					if (start < 0)
					{
						start = end = i;
					}
					else if (end - 1 == i)
					{
						end = i;
					}
					else
					{
						cache.splice(end, start - end + 1);
						start = end = i;
					}
				}
			}
		}
		else
		{
			for (i = cache.length - 1; i >= 0; i--)
			{
				if (isNaN(cache[i][field]))
				{
					if (start < 0)
					{
						start = end = i;
					}
					else if (end - 1 == i)
					{
						end = i;
					}
					else
					{
						cache.splice(end, start - end + 1);
						start = end = i;
					}
				}
			}
		}

		if (start >= 0)
			cache.splice(end, start - end + 1);
	}
	
	/**
	 *  Informs the series that the underlying data
	 *  in the dataProvider has changed.
	 *  This function triggers calls to the <code>updateData()</code>,
	 *  <code>updateMapping()</code>, <code>updateFilter()</code>,
	 *  and <code>updateTransform()</code> methods on the next call
	 *  to the <code>commitProperties()</code> method.
	 *	If any data effects are assigned to any elements of the chart,
	 *  this method also triggers the show and hide effects.
	 */
	protected function invalidateData(invalid:Boolean = true):void
	{
		if (invalid)
		{
			_bDataDirty = true;
			_bMappingDirty = true;
			_bFilterDirty = true;
			_bTransformDirty=true;
			
			invalidateTransitions();
			invalidateDisplayList();
		}
		else
		{
			_bDataDirty = false;
		}
	}

	/**
	 *  Informs the series that the mapping of the data into numeric values
	 *  has changed and must be recalculated.
	 *  Calling this function triggers calls to the <code>updateMapping()</code>,
	 *  <code>updateFilter()</code>, and <code>updateTransform()</code> methods
	 *  on the next call to the <code>commitProperties()</code> method.
	 *  If any data effects are assigned to any elements of the chart,
	 *  this method also triggers the show and hide effects.
	 */
	protected function invalidateMapping(invalid:Boolean = true):void
	{
		if (invalid)
		{
			_bDataDirty = true; // for transitions to work, we need a new cache
			_bMappingDirty = true;
			_bFilterDirty = true;
			_bTransformDirty = true;
			
			invalidateTransitions();
			invalidateDisplayList();
		}
		else
		{
			_bMappingDirty = true;
		}
	}

	/**
	 *  Informs the series that the filter of the data against the axes
	 *  has changed and must be recalculated.
	 *  Calling this method triggers calls to the <code>updateFilter()</code>
	 *  and <code>updateTransform()</code> methods on the next call
	 *  to the <code>commitProperties()</code> method.
	 *  If any data effects are assigned to any elements of the chart,
	 *  this method also triggers the show and hide effects.
	 */
	protected function invalidateFilter(invalid:Boolean = true):void
	{
		if (invalid)
		{
			_bFilterDirty = true;
			_bTransformDirty = true;
			
			invalidateTransitions();
			invalidateDisplayList();
		}
		else
		{
			_bFilterDirty = false;
		}
	}

	/**
	 *  Informs the series that the transform the data to screen coordinates
	 *  has changed and must be recalculated.
	 *  Calling this function triggers a call to the
	 *  <code>updateTransform()</code> method on the next call
	 *  to the <code>commitProperties()</code> method. 
	 */
	protected function invalidateTransform(invalid:Boolean = true):void
	{
		if (invalid)		
		{
			_bTransformDirty = true;
		
			invalidateDisplayList();
		}
		else
		{
			_bTransformDirty = false;
		}
	}

	/**
	 *  Informs the series that a significant change has occured
	 *  in the display of data.
	 *  This triggers any showData and hideData effects.  
	*/
	protected function invalidateTransitions():void
	{
		if (_bHideTransitionDirty == false)
		{
			_previousTransitionData = renderData;
						
			var s:ChartBase = chart;
			if (s)
				s.hideData();

			_bHideTransitionDirty = true;
		}		
	}
	
	/**
	 *  This method is called when the underlying data that the series represents
	 *  has changed and needs to be reloaded out of the data provider.
	 *  If you implement custom series types, you should override this method
	 *  and load all data necessary to render the series
	 *  out of the backing data provider.
	 *  You must also be sure to call the <code>super.updateData()</code> method
	 *  in your subclass.
	 *  You do not generally call this method directly.
	 *  Instead, to guarantee that your data has been updated
	 *  at a given point, call the <code>validateData()</code> method
	 *  of the Series class.
	 */
	protected function updateData():void
	{
		_bDataDirty = false;
	}
	
	/**
	 *  This method is called when the underlying data the series represents
	 *  needs to be mapped to numeric representations.
	 *  This can happen either because the underlying data has changed,
	 *  or because the axes used to render the series have changed
	 *  in some relevant way.
	 *  If you implement a custom series, you should override this method
	 *  and convert the data represented into numeric values
	 *  using the <code>mapCache()</code> method of the axes
	 *  managed by its associated DataTransform.
	 *  You must also be sure to call the <code>super.updateMapping()</code> method
	 *  in your subclass.
	 *  You should not generally call this method directly.
	 *  Instead, to guarantee that your data has been mapped
	 *  at a given point, call the <code>validateData()</code> method
	 *  of the Series class.
	 *  You can generally assume that your <code>updateData()</code> method
	 *  has been called prior to this method, if necessary.
	 */
	protected function updateMapping():void
	{
		_bMappingDirty = false;
	}
	
	/**
	 *  This method is called when the underlying data the series represents
	 *  needs to be filtered against the ranges represented by the axes
	 *  of the associated DataTransform.
	 *  This can happen either because the underlying data has changed,
	 *  or because the range of the associated axes have changed.
	 *	If you implement a custom series type, you should override this method
	 *  and filter out any outlying data using the <code>filterCache()</code>
	 *  method of the axes managed by its associated DataTransform.  
	 *	The <code>filterCache()</code> method converts any values
	 *  out of range to <code>NaN</code>.
	 *  You must be sure to call the <code>super.updateFilter()</code> method
	 *  in your subclass.
	 *  You should not generally call this method directly.
	 *  Instead, if you need to guarantee that your data has been filtered
	 *  at a given point, call the <code>validateTransform()</code> method
	 *  of the Series class.
	 *  You can generally assume that your <code>updateData()</code>
	 *  and <code>updateMapping()</code> methods have been called
	 *  prior to this method, if necessary.
	 */
	protected function updateFilter():void
	{
		_bFilterDirty = false;
	}
	
	/**
	 *  This method is called when the underlying data the series represents
	 *  needs to be transformed from data to screen values
	 *  by the axes of the associated DataTransform. 
	 *  This can happen either because the underlying data has changed,
	 *  because the range of the associated axes have changed,
	 *  or because the size of the area on screen has changed.
	 *	If you implement a custom series type, you should override this method
	 *  and transform the data using the <code>transformCache()</code> method
	 *  of the associated DataTransform.  
	 *	You must be sure to call the <code>super.updateTransform()</code> method
	 *  in your subclass.
	 *  You should not generally call this method directly.
	 *  Instead, if you need to guarantee that your data has been filtered
	 *  at a given point, call the <code>valiateTransform()</code> method
	 *  of the Series class.
	 *  You can generally assume that your <code>updateData()</code>,
	 *  <code>updateMapping()</code>, and <code>updateFilter()</code> methods
	 *  have been called prior to this method, if necessary.
	 */
	protected function updateTransform():void
	{
		_bTransformDirty = false;
	}
		
	/**
	 *  Calls the series's <code>updateFilter()</code> and
	 *  <code>updateTransform()</code> functions, if necessary.
	 *  This function is called automatically by the series
	 *  during commitProperties(), as necessary,
	 *  but a derived series might call it explicitly,
	 *	if the generated values are needed at an explicit time.
	 *  Filtering and transforming of data relies on specific values
	 *  calculated by the axes, which can, in turn,
	 *  depend on the data displayed in the chart.
	 *	Calling this function at the wrong time might result
	 *  in extra work being done if those values are updated.
	 */
	protected function validateTransform():void
	{
		if (dataTransform)
		{
			if (_bFilterDirty)
				updateFilter();

			if (_bTransformDirty)
				updateTransform();
		}
	}

	/**
	 *  Calls the series's <code>updateData()</code> and
	 *  <code>updateMapping()</code> functions, if necessary.
	 *  This function is called automatically by the series
	 *  during commitProperties*(, as necessary,
	 *  but a derived series might call it explicitly
	 *	if the generated values are needed at an explicit time.
	 *  Loading and mapping of data against the axes is defined
	 *  to be acceptable at any point by the axes;
	 *  this function is safe to call explicitly at any point.
	 */
	protected function validateData():void
	{
		if (dataTransform)
		{
			if (_bDataDirty)
				updateData();

			if (_bMappingDirty)
				updateMapping();
		}
	}

	/**
	 *  Returns a copy of the data needed to represent this series's data.
	 *  This method is called by SeriesEffects to capture the before
	 *  and after state of the series for animation.
	 * 
	 *  <p>If you implement a custom series type, you generally do not override this method.
	 *	Instead, you 
	 *  should override the <code>renderData()</code> accessor.</p>
	 *
	 *  @param type Indicates whether the effect is requesting
	 *  a description of the data being hidden, or the new data being shown.
	 */
	public function getRenderDataForTransition(type:String):Object
	{
		if (type == "hide")
		{
			return _previousTransitionData;
		}
		else
		{
			validateData();
			validateTransform();
			return renderData;
		}
	}

	/**
	 *  Fills in the <code>elementBounds</code>, <code>bounds</code>,
	 *  and <code>visibleBounds</code> properties of a renderData
	 *  structure generated by this series.
	 *  Effect classes call this function to fill in these fields
	 *  for use in implementing various effect types.
	 *  Derived classes should implement this function
	 *  to generate the bounds of the series's items only when requested.
	 */
	public function getElementBounds(renderData:Object):void
	{
	}

	/**
	 *  Called by the SeriesInterpolate effect to end an interpolation.
	 *  The effect uses this method to complete the interpolation
	 *  and clean up any temporary state associated with the effect.
	 */
	public function endInterpolation(interpolationData:Object):void
	{
		transitionRenderData = null;
	}
	
	/**
	 *  Called by the SeriesInterpolate effect to initiate an interpolation.
	 *  The effect passes in the source and destination data
	 *  for the series to interpolate between.
	 *  The effect passes the return value of this method
	 *  repeatedly to the series's <code>interpolate()</code> method
	 *  to advance the animation of the duration of the effect. 
	 *  The series calculates the data it needs to 
	 *  perform the interpolation and returns it in this method.
	 * 
	 *  @param sourceRenderData The source data for the series to interpolate between.
	 * 
	 *  @param destRenderData The destination data for the series to interpolate between.
	 * 
	 *  @return The data the series needs to perform the interpolation.
	 */
	public function beginInterpolation(sourceRenderData:Object,
									   destRenderData:Object):Object
	{
		return null;
	}

	/**
	 *  Helper function for Series to implement the interpolation effect.
	 *  A custom series can call this function from its
	 *  <code>beginInterpolation()</code> method to initialize
	 *  a data structure to interpolate  an arbitrary set
	 *  of numeric properties over the life of the effect.
	 *  You can pass that data structure to the
	 *  <code>applyInterpolation()</code> utility method to actually modify
	 *  the values when your <code>interpolate()</code> method is called.
	 *
	 *  @param srcCache An array of objects whose fields
	 *  contain the beginning values for the interpolation.
	 *
	 *  @param dstCache An array of objects whose fields
	 *  contain the ending values for the interpolation.
	 *
	 *  @param iProps A hashtable whose keys identify the names
	 *  of the properties from the cache to be interpolated.
	 *
	 *  @param cacheType The class to instantiate that holds the delta values
	 *  computed for the interpolation.
	 *  Typically this is <code>null</code>,
	 *  in which case a generic Object is used.
	 *
	 *  @param customData An object containing series-specific data.
	 *  When the initialization process encounters a missing value,
	 *  it calls the series's <code>getMissingInterpolationValues()</code>
	 *  method to fill in the missing value.
	 *  This custom data is passed to that method,
	 *  and can be used to pass through arbitrary parameters.
	 *
	 *  @returns A data structure that can be passed
	 *  to the <code>applyInterpolation()</code> method
	 * 
	 */
	protected function initializeInterpolationData(
							srcCache:Array, dstCache:Array,
							iProps:Object, cacheType:Class = null,
							customData:Object = null):Object
	{
		var interpolationCache:Array = [];
		var deltaCache:Array = [];
		var n:int = Math.max(srcCache.length, dstCache.length);

		var interpolationSource:Array = [];
		
		if (cacheType == null)
			cacheType = Object;
			
		for (var i:int = 0; i < n; i++)
		{
			var src:ChartItem = srcCache[i];
			var dst:ChartItem = dstCache[i];

			var iobj:ChartItem = dst == null ? src.clone() : dst.clone();
			var iSrc:ChartItem = src == null ? dst.clone() : src.clone();
			
			var delta:ChartItem = new cacheType();
			var populated:Boolean = false;
			var missingSrcValues:Object = {};
			var missingDestValues:Object = {};
			var bNeedMissingValues:Boolean = false;
			var aProp:String;
			var srcValue:Number;
			var dstValue:Number;
			
			for (aProp in iProps)
			{
				if (src)
					srcValue = src[aProp];
				else
					srcValue = NaN;
				
				if (dst)
					dstValue = dst[aProp];
				else
					dstValue = NaN;

				if (isNaN(srcValue) || isNaN(dstValue))
				{
					bNeedMissingValues = true;
					missingSrcValues[aProp] = srcValue;
					missingDestValues[aProp] = dstValue;					
				}
				else
				{
					iSrc[aProp] = srcValue;
					iobj[aProp] = srcValue;
					delta[aProp] = dstValue - srcValue;				
				}
			}

			if (bNeedMissingValues)
			{
				getMissingInterpolationValues(missingSrcValues, srcCache,
											  missingDestValues, dstCache,
											  i, customData);

				for (aProp in missingSrcValues)
				{
					srcValue = missingSrcValues[aProp];
					iSrc[aProp] = srcValue;
					iobj[aProp] = srcValue;
					delta[aProp] = missingDestValues[aProp] - srcValue;				
				}
			}

			interpolationSource[i] = iSrc;
			interpolationCache[i] = iobj;
			deltaCache[i] = delta;			
		}
						
		var interpolationData:Object =
		{
			cache: interpolationCache,
			interpolationSource: interpolationSource,
			deltaCache: deltaCache,
			properties: iProps
		}
			
		return interpolationData;
	}

	/**
	 *  Fills in missing values in an interpolation structure.
	 *  When a series calls the <code>initializeInterpolationData()</code> method,
	 *  it passes in an Array of source and destination values
	 *  for the interpolation.
	 *  If either of those two Arrays are incomplete, the series needs
	 *  to provide "appropriate" placeholder values for the interpolation.
	 *  How those placeholder values are determined
	 *  is specific to the series type.
	 *  Series extenders should override this method
	 *  to provide those placeholder values.
	 *
	 *  @param sourceProps An object containing the source values
	 *  being interpolated for a particular item.
	 *  When this method exits, all properties in this Object
	 *  should have non-<code>NaN</code> values.
	 *
	 *  @param srcCache The Array of source chart items that are being interpolated.
	 *
	 *  @param destProps An Object containing the destination values
	 *  that are being interpolated for a particular item.
	 *  When this method exits, all properties in this Object
	 *  should have non-<code>NaN</code> values.
	 *
	 *  @param destCache The Array of destination chart items that are being interpolated.
	 *
	 *  @param index The index of the item that is being populated in the cache.  
	 *
	 *  @param customData The data that was passed by the series
	 *  into the <code>initializeInterpolationData()</code> method.
	 */
	protected function getMissingInterpolationValues(
							sourceProps:Object, srcCache:Array,
							destProps:Object, destCache:Array,
							index:Number, customData:Object):void
	{
	}

	/**
	 *  Called by the SeriesInterpolate effect to advance an interpolation.
	 *  The effect calls this once per frame until the interpolation
	 *  is complete.
	 *  The series is responsible for using the parameters
	 *  to render the interpolated values.
	 *  By default, the series assumes that <code>interpolationData</code>
	 *  is a data structure returned by the
	 *  <code>initializeInterpolationData()</code> method, and passes it
	 *  through to the <code>applyInterpolation()</code> method.
	 *
	 *  @param interpolationValues An Array of Numbers, each ranging
	 *  from 0 to 1, where the <i>n</i>th number indicates the percentage
	 *  of the way the <i>n</i>th value in the data series should be
	 *  interpolated between the start and end values.
	 *
	 *  @param interpolationData The data returned from the
	 *  <code>beginInterpolation()</code> method.
	 */
	public function interpolate(interpolationValues:Array,
								interpolationData:Object):void
	{
		applyInterpolation(interpolationData, interpolationValues);
	}

	/**
	 *  @private
	 */
	private function applyInterpolation(interpolationData:Object,
										interpolationValues:Array):void
	{		
		var n:int = interpolationValues.length;
		var srcCache:Array = interpolationData.interpolationSource;
		var deltaCache:Array = interpolationData.deltaCache;
		var interpolationCache:Array = interpolationData.cache;
		var iProps:Object = interpolationData.properties;

		n = interpolationCache.length;
		for (var i:int = 0; i < n; i++)
		{
			var interpValue:Number = interpolationValues[i];
			var src:Object = srcCache[i];
			var delta:Object = deltaCache[i];			
			var interp:Object  = interpolationCache[i];
			for (var aProp:String in iProps)
			{
				interp[aProp] = src[aProp] + delta[aProp] * interpValue;	
			}
		}
	}
	
	/**
	 *  Updates the Legend items when the series display name changes
	 *  by dispatching a new LegendDataChanged event.
	 */
	protected function legendDataChanged():void
	{
		var c:ChartBase = chart;
		if (c)
			c.legendDataChanged();
	}

	//--------------------------------------------------------------------------
	//
	//  Overridden event handlers: ChartElement
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override protected function dataChanged():void
	{
		invalidateData();

		super.dataChanged();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Event handlers
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private function transformChangeHandler(event:FlexEvent):void
	{
		invalidateTransform();
	}
}

}
